package indi.h.devmg.acquisition.snmp;

import java.io.Closeable;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;

import org.snmp4j.CommunityTarget;
import org.snmp4j.PDU;
import org.snmp4j.Snmp;
import org.snmp4j.Target;
import org.snmp4j.TransportMapping;
import org.snmp4j.UserTarget;
import org.snmp4j.event.ResponseEvent;
import org.snmp4j.event.ResponseListener;
import org.snmp4j.mp.MPv3;
import org.snmp4j.mp.SnmpConstants;
import org.snmp4j.security.SecurityModels;
import org.snmp4j.security.SecurityProtocols;
import org.snmp4j.security.USM;
import org.snmp4j.smi.Address;
import org.snmp4j.smi.GenericAddress;
import org.snmp4j.smi.OID;
import org.snmp4j.smi.OctetString;
import org.snmp4j.smi.VariableBinding;
import org.snmp4j.transport.DefaultUdpTransportMapping;
import org.snmp4j.util.DefaultPDUFactory;
import org.snmp4j.util.TableEvent;
import org.snmp4j.util.TableListener;
import org.snmp4j.util.TableUtils;

import indi.h.devmg.acquisition.snmp.beans.SnmpParam;

/**
 * Snmp获取信息过程 开启snmp 创建target 传输pdu包 获取返回的response
 * 
 * @author hdy
 *
 */
public class SnmpUtil implements Closeable {
	private Snmp snmp = null;
	private TransportMapping<?> transport = null;
	private int version;
	private ReentrantLock rLock;
	private boolean close;

	protected SnmpUtil() {
		// TODO Auto-generated constructor stub
		this.close = false;
		this.rLock = new ReentrantLock();
	}

	/**
	 * 获取一个v2的snmp对象,可能为已存在对象
	 * 
	 * @param version
	 * @return
	 */
	public synchronized static SnmpUtil getInstance() {
		return getInstance(SnmpVersion.V2C);
	}

	/**
	 * 获取一个snmp对象,可能为已存在对象
	 * 
	 * @param version
	 * @return
	 */
	public synchronized static SnmpUtil getInstance(SnmpVersion version) {
		if (version.getSnmpUtil() != null && version.getSnmpUtil().isClose() == false)
			return version.getSnmpUtil();
		SnmpUtil snmpUtil = new SnmpUtil();
		try {
			snmpUtil.init(version);
			version.setSnmpUtil(snmpUtil);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return snmpUtil;
	}

	/**
	 * 创建一个v2的snmp对象,一个全新的对象
	 * 
	 * @param version
	 * @return
	 */
	public synchronized static SnmpUtil createInstance() {
		SnmpUtil snmpUtil = new SnmpUtil();
		try {
			snmpUtil.init(SnmpVersion.V2C);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return snmpUtil;
	}

	/**
	 * 创建一个snmp对象,一个全新的对象
	 * 
	 * @param version
	 * @return
	 */
	public synchronized static SnmpUtil createInstance(SnmpVersion version) {
		SnmpUtil snmpUtil = new SnmpUtil();
		try {
			snmpUtil.init(version);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return snmpUtil;
	}

	/**
	 * 进行同步或异步snmp的walk模式获取设备信息
	 * 
	 * @param addr
	 * @param oids
	 * @param listener
	 *            如果不为null,则无论异步同步都会触发
	 * @return
	 * @throws IOException
	 */
	public List<TableEvent> syncWalkSnmp(SnmpParam s, TableListener t) throws IOException {
		String addr=s.getSnmpUrl();
		Target target = createTarget(addr, s);
		TableUtils utils = new TableUtils(snmp, new DefaultPDUFactory(s.getPduType()));
		if (s.getMaxPduSize() != null)
			utils.setMaxNumRowsPerPDU(s.getMaxPduSize());
		if (s.isSync()) {
			final ReentrantLock lock = this.rLock;
			try {
				lock.lock();
				List<TableEvent> ls = utils.getTable(target, s.getOids(), s.getLowerItem(), s.getUpperItem());
				if (t != null) {
					for (TableEvent l : ls) {
						if (!t.next(l)) {
							break;
						}
					}
					t.finished(null);
				}
				return ls;
			} finally {
				lock.unlock();
			}
		} else {
			utils.getTable(target, s.getOids(), t, s.getUserObject(), s.getLowerItem(), s.getUpperItem());
			return null;
		}
	}

	/**
	 * 初始化snmp对象
	 * 
	 * @param ver
	 *            snmp版本
	 * @throws IOException
	 */
	public void init(SnmpVersion version) throws IOException {
		this.version = version.getVersion();
		this.transport = new DefaultUdpTransportMapping();
		this.snmp = new Snmp(transport);
		if (this.version == SnmpConstants.version3) {
			USM usm = new USM(SecurityProtocols.getInstance(), new OctetString(MPv3.createLocalEngineID()), 0);
			SecurityModels.getInstance().addSecurityModel(usm);
		}
		this.transport.listen();
	}

	/**
	 * 同步或异步获取节点信息
	 * 
	 * @param isSync
	 *            是否启用同步模式
	 * @param pdu
	 *            oid的封装包
	 * @param addr
	 *            snmp目标地址
	 * @param r
	 *            异步访问的回调接口
	 * @param s
	 *            snmp参数
	 * @return
	 * @throws IOException
	 */
	public ResponseEvent syncUdpMessage(PDU pdu, SnmpParam s, ResponseListener r) throws IOException {
		String addr=s.getSnmpUrl();
		Target target = createTarget(addr, s);
		if (s.isSync()) {
			final ReentrantLock lock = this.rLock;
			try {
				lock.lock();
				ResponseEvent send = this.snmp.send(pdu, target);
				if (r != null) {
					r.onResponse(send);
				}
				return send;
			} finally {
				lock.unlock();
			}
		} else {
			this.snmp.send(pdu, target, null, r);
			return null;
		}
	}

	/**
	 * 
	 * @param oid
	 *            oid
	 * @param addr
	 *            ip地址
	 * @param isSync
	 *            是否同步
	 * @param r
	 *            异步回调接口
	 * @return
	 * @throws IOException
	 */
	public ResponseEvent syncUdpMessage(String oid, boolean isSync, ResponseListener r)
			throws IOException {
		return syncUdpMessage(new String[] { oid }, isSync, r);
	}

	/**
	 * 
	 * @param oids
	 *            oid列表
	 * @param addr
	 *            ip地址
	 * @param isSync
	 *            是否同步
	 * @param r
	 *            异步回调接口
	 * @return
	 * @throws IOException
	 */
	public ResponseEvent syncUdpMessage(String[] oids, boolean isSync, ResponseListener r)
			throws IOException {
		SnmpParam sp = new SnmpParam();
		sp.setSync(isSync);
		return syncUdpMessage(oids, sp, r);
	}

	/**
	 * 
	 * @param oids
	 *            oid列表
	 * @param addr
	 *            ip地址
	 * @param s
	 *            snmp参数
	 * @param r
	 *            异步回调接口
	 * @return
	 * @throws IOException
	 */
	public ResponseEvent syncUdpMessage(String[] oids, SnmpParam s, ResponseListener r)
			throws IOException {
		s.setOids(oids);
		PDU pdu = new PDU();
		pdu.setType(s.getPduType());
		VariableBinding[] vbs = new VariableBinding[oids.length];
		int i = 0;
		for (OID o : s.getOids()) {
			vbs[i] = new VariableBinding(o);
			i++;
		}
		pdu.addAllOIDs(vbs);
		return syncUdpMessage(pdu, s, r);
	}

	/**
	 * @param addr
	 *            ip地址
	 * @param s
	 *            snmp参数
	 * @param r
	 *            异步回调接口
	 * @return
	 * @throws IOException
	 */
	public ResponseEvent syncUdpMessage(SnmpParam s, ResponseListener r) throws IOException {
		PDU pdu = new PDU();
		pdu.setType(s.getPduType());
		VariableBinding[] vbs = new VariableBinding[s.getOids().length];
		int i = 0;
		for (OID o : s.getOids()) {
			vbs[i] = new VariableBinding(o);
			i++;
		}
		pdu.addAllOIDs(vbs);
		return syncUdpMessage(pdu, s, r);
	}

	/**
	 * 创建target对象
	 * 
	 * @param addr
	 *            获取snmp目标的地址
	 * @param snmpParam
	 *            snmp参数
	 * @return
	 * @throws IOException
	 */
	private Target createTarget(String addr, SnmpParam snmpParam) throws IOException {
		Address targetAddress = GenericAddress.parse(addr);
		Target target = null;
		if (this.version == SnmpConstants.version3) {
			snmp.getUSM().addUser(snmpParam.getV3Code(), snmpParam.getV3User());
			target = new UserTarget();
			((UserTarget) target).setSecurityLevel(snmpParam.getSecurityLevel());
			((UserTarget) target).setSecurityName(snmpParam.getV3Code());
			target.setVersion(SnmpConstants.version3);
		} else {
			target = new CommunityTarget();
			target.setVersion(this.version);
			((CommunityTarget) target).setCommunity(snmpParam.getCommunity());
		}
		target.setAddress(targetAddress);
		target.setRetries(snmpParam.getRetries());
		target.setTimeout(snmpParam.getTimeOut());
		return target;
	}

	/**
	 * 是否关闭
	 * 
	 * @return
	 */
	public boolean isClose() {
		return close;
	}

	/**
	 * 关闭snmp
	 */
	@Override
	public synchronized void close() throws IOException {
		// TODO Auto-generated method stub
		try {
			if (this.transport != null)
				this.transport.close();
			if (this.snmp != null)
				this.snmp.close();
		} catch (Exception e) {
			// TODO: handle exception
			if (this.snmp != null) {
				try {
					this.snmp.close();
				} catch (IOException e1) {
					// TODO Auto-generated catch block
					e1.printStackTrace();
				}
			}
			e.printStackTrace();
		} finally {
			close = true;
		}
	}

}
